﻿//   AppStorageServiceApplication.cs
//   Copyright (c) 2012 Eswar Prakash
// 
//    This library is free software; you can redistribute it and/or modify  
//    it  under the terms of the GNU Lesser General Public License version  
//    2.1 as published by the Free Software Foundation.                     
//                                                                          
//    This library is distributed in the hope that it will be useful, but   
//    WITHOUT ANY WARRANTY; without even the implied warranty of            
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     
//    Lesser General Public License for more details.                       
//                                                                          
//    You should have received a copy of the GNU Lesser General Public      
//    License along with this library; if not, write to the Free Software   
//    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  
//    USA
namespace CKSAS.AppStorage.SharedService
{
    using System;
    using Microsoft.SharePoint.Administration;
    using System.ServiceModel;
    using System.Runtime.InteropServices;
    using Microsoft.SharePoint.Utilities;
    using System.Security.Principal;
    using Core.Diagnostics;
    using System.Reflection;
    using System.Security.Principal;

    /// <summary>
    /// Implements application storage service
    /// </summary>
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession,
        ConcurrencyMode = ConcurrencyMode.Multiple,
        IncludeExceptionDetailInFaults = true)]
    [Guid("8176b564-7770-46d0-a8ca-584221acc325")]
    [IisWebServiceApplicationBackupBehavior]
    internal sealed class AppStorageServiceApplication : SPIisWebServiceApplication, IAppStorageService
    {
        /// <summary>
        /// Private persisted member to hold an instance of the application service database
        /// </summary>
        [Persisted]
        private AppStorageServiceDatabase database = null;

        /// <summary>
        /// Private constant member to hold the display type name of the service application
        /// </summary>
        private const string APPLICATION_TYPENAME = @"Application Storage Service Application";

        /// <summary>
        /// Initializes a new instance of the <see cref="AppStorageServiceApplication"/> class.
        /// </summary>
        public AppStorageServiceApplication()
            : base()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AppStorageServiceApplication"/> class.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="service"></param>
        /// <param name="applicationPool"></param>
        public AppStorageServiceApplication(string name, SPIisWebService service,
                                            SPIisWebServiceApplicationPool applicationPool)
            : base(name, service, applicationPool)
        {
        }

        /// <summary>
        /// Gets the default name of the endpoint.
        /// </summary>
        /// <value>
        /// The default name of the endpoint.
        /// </value>
        protected override string DefaultEndpointName
        {
            get { return "http"; }
        }

        /// <summary>
        /// Gets the display name that describes the object type in the administrative user interface.
        /// </summary>
        /// <returns>A string that contains the name of the object type.</returns>
        public override string TypeName
        {
            get { return APPLICATION_TYPENAME; }
        }

        /// <summary>
        /// </summary>
        /// <returns>Returns <see cref="T:System.String"/>.</returns>
        protected override string InstallPath
        {
            get { return SPUtility.GetGenericSetupPath(@"WebServices\AppStorage"); }
        }

        /// <summary>
        /// </summary>
        /// <returns>Returns <see cref="T:System.String"/>.</returns>
        protected override string VirtualPath
        {
            get { return @"AppStorage.svc"; }
        }

        /// <summary>
        /// </summary>
        /// <returns>Returns <see cref="T:System.Guid"/>.</returns>
        public override Guid ApplicationClassId
        {
            get { return new Guid("ab0612d3-e82a-44fa-96af-d38890b0b775"); }
        }

        /// <summary>
        /// </summary>
        /// <returns>Returns <see cref="T:System.Version"/>.</returns>
        public override Version ApplicationVersion
        {
            get { return new AssemblyName(Assembly.GetExecutingAssembly().FullName).Version; }
        }

        /// <summary>
        /// Provisions this instance.
        /// </summary>
        public override void Provision()
        {
            base.Status = SPObjectStatus.Provisioning;
            this.Update();
            try
            {
                try
                {
                    this.database.Provision();
                }
                catch
                {
                    var database = this.database;
                    this.database = null;
                    this.Update();

                    try
                    {
                        database.Delete();
                    }
                    catch
                    {
                    }
                    throw;
                }
                base.Provision();
            }
            catch
            {
                base.Status = SPObjectStatus.Offline;
                this.Update();
                throw;
            }
            base.Status = SPObjectStatus.Online;
            this.Update();
        }

        /// <summary>
        /// Unprovisions the specified delete data.
        /// </summary>
        /// <param name="deleteData">if set to <c>true</c> [delete data].</param>
        public override void Unprovision(bool deleteData)
        {
            base.Status = SPObjectStatus.Unprovisioning;
            this.Update();
            if (deleteData && this.database != null)
            {
                this.database.Unprovision();
            }
            base.Unprovision(deleteData);
            base.Status = SPObjectStatus.Disabled;
            this.Update();
        }

        /// <summary>
        /// Deletes this instance.
        /// </summary>
        public override void Delete()
        {
            Guid databaseId = Guid.Empty;
            if (this.database != null)
            {
                databaseId = this.database.Id;
                this.database = null;
                this.Update();
            }

            if (databaseId != Guid.Empty)
            {
                var serviceDatabase = (AppStorageServiceDatabase)base.Farm.GetObject(databaseId);
                if (serviceDatabase != null)
                {
                    serviceDatabase.Delete();
                }
            }

            base.Delete();
        }

        /// <summary>
        /// Gets the database.
        /// </summary>
        internal AppStorageServiceDatabase Database
        {
            get { return this.database; }

            set { this.database = value; }
        }

        /// <summary>
        /// Creates a new auto numbering service application given the parameters
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="service">The service.</param>
        /// <param name="applicationPool">The application pool.</param>
        /// <returns></returns>
        internal static AppStorageServiceApplication Create(string name, AppStorageService service,
                                                            SPIisWebServiceApplicationPool applicationPool,
                                                            SPDatabaseParameters databaseParameters)
        {
            if (String.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException("name");
            }

            if (service == null)
            {
                throw new ArgumentNullException("service");
            }

            if (databaseParameters == null)
            {
                throw new ArgumentNullException("databaseParameters");
            }

            if (applicationPool == null)
            {
                throw new ArgumentNullException("applicationPool");
            }

            AppStorageServiceDatabase database = null;
            try
            {
                LoggingUtilities.WriteTraceEntry(DiagnosticsAreas.CKSAS, DiagnosticCategories.ServiceProvisioning,
                                                 TraceSeverity.Medium, "Creating service database: {0}",
                                                 databaseParameters.Database);
                database = AppStorageServiceDatabase.Create(databaseParameters);
                database.Update();
            }
            catch (Exception ex)
            {
                LoggingUtilities.WriteTraceEntry(DiagnosticsAreas.CKSAS, DiagnosticCategories.ServiceProvisioning,
                                                 TraceSeverity.Unexpected,
                                                 "Unexpected error provisioning service database: {0}",
                                                 databaseParameters.Database);
                LoggingUtilities.WriteTraceEntry(DiagnosticsAreas.CKSAS, DiagnosticCategories.ServiceProvisioning,
                                                 TraceSeverity.Unexpected, "Exception details: {0} Stack Trace: {1}",
                                                 ex.Message, ex.StackTrace);
                if (database != null)
                {
                    database.Delete();
                }
            }

            AppStorageServiceApplication application = null;
            try
            {
                application = new AppStorageServiceApplication(name, service, applicationPool);
                application.Database = database;
                application.Update();
            }
            catch
            {
                if (database != null)
                {
                    database.Delete();
                }

                if (application != null)
                {
                    application.Delete();
                }
            }

            return application;
        }

        /// <summary>
        /// </summary>
        /// <returns>Returns <see cref="T:Microsoft.SharePoint.Administration.SPAdministrationLink"/>.</returns>
        public override SPAdministrationLink ManageLink
        {
            get
            {
                // At the moment we don't have a need to create to a service management page
                // may come back to this later if required
                return null;
                /*return
                    new SPAdministrationLink("/_layouts/CKSAS/AppStorage/ManageServiceApplication.aspx?ApplicationId=" +
                                             base.Id.ToString()); */
            }
        }

        /// <summary>
        /// </summary>
        /// <param name="processSecurityIdentifier"></param>
        protected override void OnProcessIdentityChanged(SecurityIdentifier processSecurityIdentifier)
        {
            base.OnProcessIdentityChanged(processSecurityIdentifier);
            if (this.database != null)
            {
                this.database.GrantProcessAccountAccess(processSecurityIdentifier);
                LoggingUtilities.WriteTraceEntry(DiagnosticsAreas.CKSAS, DiagnosticCategories.AppStorageService,
                                                 TraceSeverity.High,
                                                 "Process account access assigned to {0} on the service database {1}",
                                                 processSecurityIdentifier.ToString(),
                                                 this.database.DatabaseConnectionString);
            }
        }

        /// <summary>
        /// Gets a stored object given its key in the storage.
        /// </summary>
        /// <param name="partition">The partition for which the storage item needs to be returned.</param>
        /// <param name="name">The unique name of the storage item in the storage.</param>
        /// <returns>
        /// An instance of <see cref="StorageItem"/> containing information about the storage item
        /// </returns>
        StorageItem IAppStorageService.GetStorageItem(StoragePartition partition, string name)
        {
            using (var result = this.Database.GetStorageItem(partition.WebApplicationId, partition.SiteId,
                                                             partition.WebId, partition.ListId, name))
            {
                if (result.DataReader != null)
                {
                    while (result.DataReader.Read())
                    {
                        return new StorageItem()
                         {
                             Id = (int)result.DataReader["Id"],
                             TypeFullName = (string)result.DataReader["TypeFullName"],
                             Data = (byte[])result.DataReader["Data"],
                             CreatedBy = (string)result.DataReader["CreatedBy"],
                             CreatedDate = (DateTime)result.DataReader["CreatedDate"],
                             ModifiedBy = (string)result.DataReader["ModifiedBy"],
                             ModifiedDate = (DateTime)result.DataReader["ModifiedDate"]
                         };
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// Adds the stored item to the underlying storage
        /// </summary>
        /// <param name="partition">The partition for which the storage item is indeed.</param>
        /// <param name="name">The unique name of the item in the storage.</param>
        /// <param name="item">The item information that needs to be stored.</param>
        /// <returns>
        /// An instance of <see cref="StorageItem"/> containing the newly created storage item details
        /// </returns>
        StorageItem IAppStorageService.AddStorageItem(StoragePartition partition, string name, StorageItem item)
        {
            using (var result = this.Database.AddStorageItem(partition.WebApplicationId, partition.SiteId, partition.WebId,
                partition.ListId, name, item.TypeFullName, item.CreatedBy, item.Data))
            {
                if (result.DataReader != null)
                {
                    while (result.DataReader.Read())
                    {
                        item.Id = (int)result.DataReader["Id"];
                        item.CreatedDate = (DateTime)result.DataReader["CreatedDate"];
                        item.ModifiedBy = (string)result.DataReader["ModifiedBy"];
                        item.ModifiedDate = (DateTime)result.DataReader["ModifiedDate"];

                        // read only the first item
                        break;
                    }
                }
            }

            return item;
        }

        /// <summary>
        /// Updates the stored item in the underlying storage
        /// </summary>
        /// <param name="partition">The partition for which the storage item is indeed.</param>
        /// <param name="name">The unique name of the item in the storage.</param>
        /// <param name="item">The item information that needs to be stored.</param>
        /// <returns>
        /// An instance of <see cref="StorageItem"/> containing the newly created storage item details
        /// </returns>
        StorageItem IAppStorageService.UpdateStorageItem(StoragePartition partition, string name, StorageItem item)
        {
            using (var result = this.Database.UpdateStorageItem(partition.WebApplicationId, partition.SiteId, partition.WebId,
                partition.ListId, name, item.TypeFullName, item.ModifiedBy, item.Data))
            {
                if (result.DataReader != null)
                {
                    while (result.DataReader.Read())
                    {
                        item.Id = (int)result.DataReader["Id"];
                        item.CreatedDate = (DateTime)result.DataReader["CreatedDate"];
                        item.CreatedBy = (string) result.DataReader["CreatedBy"];
                        item.ModifiedBy = (string)result.DataReader["ModifiedBy"];
                        item.ModifiedDate = (DateTime)result.DataReader["ModifiedDate"];

                        // read only the first item
                        break;
                    }
                }
            }

            return item;
        }

        /// <summary>
        /// Deletes the storage item from the underlying storage
        /// </summary>
        /// <param name="partition">The partition where the storage item exists.</param>
        /// <param name="name">The name of the storage item in the storage.</param>
        void IAppStorageService.DeleteStorageItem(StoragePartition partition, string name)
        {
            using (var result = this.Database.DeleteStorageItem(partition.WebApplicationId, partition.SiteId, partition.WebId,
                partition.ListId, name))
            {
            }
        }
    }
}